package com.sec.android.app.myfiles.util;

import android.app.NotificationManager;
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.Context;
import android.database.Cursor;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.StatFs;
import android.os.storage.StorageManager;
import android.os.storage.StorageVolume;
import android.text.TextUtils;
import android.util.SparseArray;

import com.sec.android.app.myfiles.R;
import com.sec.android.app.myfiles.feature.KnoxMgr;
import com.sec.android.app.myfiles.feature.PrivateModeMgr;
import com.sec.android.app.myfiles.info.AppConstants;
import com.sec.android.app.myfiles.info.AppConstants.StoragePath;
import com.sec.android.app.myfiles.info.AppFeatures;
import com.sec.android.app.myfiles.log.Log;
import com.sec.android.app.myfiles.module.abstraction.FileRecord;
import com.sec.android.app.myfiles.navigation.NavigationInfo;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.StringTokenizer;

/**
 * Created by zhitu.chen on 8/25/16.
 */
public class StorageMonitor {
    private static final String TAG = "StorageMonitor";
    private static final long SYSTEM_MEMORY_UNIT_GB = 1L * 1024L * 1024L * 1024L;

    private static boolean sSdCardMounted;
    private static Bundle sKnoxInfoForAppBundle = null;
    private static boolean sIsSdCardEncrypted = false;

    // for 3D capture application
    private static String sExtSDcardPath = AppConstants.EXT_SD_CARD_ROOT;

    private static SparseArray<String> sUsbStoragePathArray = new SparseArray<>();

    private static long sInternalTotalSize = 0;

    public interface iStorageType {
        int UNKNOWN = -1;
        int INTERNAL = 0;
        int EXTERNAL_SD = 1;
        int EXTERNAL_PRIVATE_MODE = 2;
        int EXTERNAL_USB_DRIVE_A = 3;
        int EXTERNAL_USB_DRIVE_B = 4;
        int EXTERNAL_USB_DRIVE_C = 5;
        int EXTERNAL_USB_DRIVE_D = 6;
        int EXTERNAL_USB_DRIVE_E = 7;
        int EXTERNAL_USB_DRIVE_F = 8;
    }

    public static boolean isInternalPath(String path) {
        return FileUtils.isUnderPath(StoragePath.INTERNAL_ROOT, path);
    }


    /**
     * check path is Sd card path.
     * It does not consider SD card is mounted or not.
     *
     * @param path
     * @return
     * @See isSdCardPath
     */
    public static boolean isSdCardPath(String path) {
        return FileUtils.isUnderPath(sExtSDcardPath, path);
    }

    public static boolean isCloudPath(String path) {
        return isSamsungDrivePath(path) || isGoogleDrivePath(path);
    }

    public static boolean isSamsungDrivePath(String path) {
        return FileUtils.isUnderPath(StoragePath.SAMSUNG_DRIVE_FOLDER, path);
    }

    public static boolean isGoogleDrivePath(String path) {
        return FileUtils.isUnderPath(StoragePath.GOOGLE_DRIVE_FOLDER, path);
    }

    public static boolean isCurrentStoragePath(String path) {
        return isInternalPath(path) || isMountedSdCardPath(path) || startWithUsbRoot(path) || FileUtils.isPrivateFolder(path);
    }

    /**
     * check path is Sd card path and Sd card is mounted
     *
     * @param path
     * @return
     * @See isSdCardPath
     */
    public static boolean isMountedSdCardPath(String path) {
        boolean bRet = false;

        if (isSdCardMounted()) {
            bRet = FileUtils.isUnderPath(sExtSDcardPath, path);
        }

        return bRet;
    }

    public static boolean isRemovedExtSDCard(String path) {
        boolean bRet = false;
        if (!isSdCardMounted()) {
            bRet = FileUtils.isUnderPath(sExtSDcardPath, path);
        }

        return bRet;
    }

    public static boolean isSdCardMounted() {
        return sSdCardMounted;
    }

    public static String getExtSDCardPath() {
        return sExtSDcardPath;
    }

    private static boolean isBlockExternalSD(Context context) {
        boolean bRet = false;
        return bRet;
    }

    public static boolean isExternalStorageAvailable(NavigationInfo curInfo, Context context) {
        KnoxMgr knoxMgr = KnoxMgr.getInstance(context);
        boolean supportExternalStorage = (!AppFeatures.isKnox() || knoxMgr.isSupportExternalStorage())
                && !AppFeatures.isBYOD();
        if (curInfo == null) {
            return supportExternalStorage;
        }
        return !curInfo.isCreateDocument() && supportExternalStorage;
    }

    public static boolean isUsbStorageMounted() {
        return sUsbStoragePathArray.size() > 0;
    }

    public static boolean isUsbStorageMounted(int storageType) {
        return sUsbStoragePathArray.get(storageType) != null;
    }

    public static String getUsbStoragePath(int storageType) {
        return sUsbStoragePathArray.get(storageType);
    }

    public static String getUsbStorageName(Context context, int storageType) {
        if (context != null) {
            if (StorageMonitor.isSSDPowerPacked(context)) {
                return context.getString(R.string.ssd_power_pack);
            } else {
                String usbStoragePath = getUsbStoragePath(storageType);
                if (usbStoragePath != null) {
                    return context.getString(R.string.usb_storage) + " " + usbStoragePath.substring(usbStoragePath.lastIndexOf('/') + 1);
                }
            }
        }
        return null;
    }

    public static boolean isUsbRoot(String path) {
        if (path != null && sUsbStoragePathArray != null) {
            for (int i = 0; i < sUsbStoragePathArray.size(); i++) {
                String usbPath = sUsbStoragePathArray.get(i + iStorageType.EXTERNAL_USB_DRIVE_A);
                if (usbPath != null && path.equalsIgnoreCase(usbPath)) {
                    return true;
                }
            }
        }
        return false;
    }

    public static boolean startWithUsbRoot(String path) {
        if (path != null && sUsbStoragePathArray != null) {
            for (int i = 0; i < sUsbStoragePathArray.size(); i++) {
                String usbPath = sUsbStoragePathArray.get(i + iStorageType.EXTERNAL_USB_DRIVE_A);
                if (FileUtils.isUnderPath(usbPath, path)) {
                    return true;
                }
            }
        }
        return false;
    }

    public static void updateSubSystemStorageState(Context context) {
        Log.d(TAG, "updateSubSystemStorageState()");
        sUsbStoragePathArray.clear();
        sSdCardMounted = false;
        sIsSdCardEncrypted = false;

        if (!isBlockExternalSD(context)) {
            int usbNo = iStorageType.EXTERNAL_USB_DRIVE_A;
            StorageManager storageManager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
            List<StorageVolume> storageVolumeList = SemFwWrapper.getStorageVolumes(storageManager);

            if (storageVolumeList != null) {
                for (StorageVolume storageVolume : storageVolumeList) {
                    String storagePath = SemFwWrapper.semGetPath(storageVolume);
                    String volumeState = SemFwWrapper.getState(storageVolume);

                    if (Environment.MEDIA_MOUNTED.equals(volumeState)) {
                        if (StorageVolumeUtil.isSdStorage(storageVolume)) {
                            sSdCardMounted = true;
                            sExtSDcardPath = storagePath;
                            sIsSdCardEncrypted = getSdCardEncrypted(context);
                        } else if (StorageVolumeUtil.isUsbStorage(storageVolume)) {
                            sUsbStoragePathArray.put(usbNo, storagePath);
                            usbNo++;
                        }
                    }
                }
            }

            if (sSdCardMounted) {
                Log.d(TAG, "updateSubSystemStorageState() sd card mounted.");
            } else {
                Log.d(TAG, "updateSubSystemStorageState() sd card unmounted.");
            }
            int size = sUsbStoragePathArray.size();
            for (int i = 0; i < size; i++) {
                Log.d(TAG, "updateSubSystemStorageState() usb storage mounted. - path : "
                        + sUsbStoragePathArray.get(i + iStorageType.EXTERNAL_USB_DRIVE_A));
            }
            if (size == 0) {
                Log.d(TAG, "updateSubSystemStorageState() usb storage unmounted.");
                NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
                notificationManager.cancel(0);
            }
        } else {
            Log.d(TAG, "updateSubSystemStorageState() external storage blocked");
        }
        if (isPrivateModeDirMounted(context)) {
            Log.d(TAG, "updateSubSystemStorageState() private mode mounted.");
        }
    }

    public static boolean isStorageRoot(String path) {
        boolean bRet = false;
        if (path == null || path.isEmpty()) {
            return bRet;
        }

        if (path.equalsIgnoreCase(StoragePath.INTERNAL_ROOT) ||
                path.equalsIgnoreCase(getExtSDCardPath()) ||
                path.equalsIgnoreCase(StoragePath.PRIVATE_ROOT) ||
                isUsbRoot(path)) {
            bRet = true;
        } else {
            String parent = FileRecord.getPath(path);
            if (parent != null) {
                if (parent.equalsIgnoreCase(StoragePath.ROOT)) {
                    bRet = true;
                }
            }
        }

        return bRet;
    }

    public static boolean isRemovableStorageRoot(String path) {
        boolean bRet = false;
        if (path == null || path.isEmpty()) {
            return bRet;
        }

        if (path.equalsIgnoreCase(getExtSDCardPath()) ||
                path.equalsIgnoreCase(StoragePath.PRIVATE_ROOT) ||
                isUsbRoot(path)) {
            bRet = true;
        } else {
            String parent = FileRecord.getPath(path);
            if (parent != null) {
                if (parent.equalsIgnoreCase(StoragePath.ROOT)) {
                    bRet = true;
                }
            }
        }

        return bRet;
    }

    public static boolean isStorageMounted(Context context, String path) {
        int storageType = getMatchedStorageType(path);
        return isStorageMounted(context, storageType);
    }

    public static boolean isStorageMounted(Context context, int storageType) {
        boolean mounted = false;
        switch (storageType) {
            case iStorageType.INTERNAL:
                mounted = true;
                break;
            case iStorageType.EXTERNAL_SD:
                mounted = isSdCardMounted();
                break;
            case iStorageType.EXTERNAL_PRIVATE_MODE:
                mounted = isPrivateModeDirMounted(context);
                break;
            case iStorageType.EXTERNAL_USB_DRIVE_A:
            case iStorageType.EXTERNAL_USB_DRIVE_B:
            case iStorageType.EXTERNAL_USB_DRIVE_C:
            case iStorageType.EXTERNAL_USB_DRIVE_D:
            case iStorageType.EXTERNAL_USB_DRIVE_E:
            case iStorageType.EXTERNAL_USB_DRIVE_F:
                mounted = isUsbStorageMounted(storageType);
                break;
        }
        return mounted;
    }

    public static String getStoragePath(Context context, int storageType) {
        String path = null;
        switch (storageType) {
            case iStorageType.INTERNAL:
                path = StoragePath.INTERNAL_ROOT;
                break;
            case iStorageType.EXTERNAL_SD:
                if (isSdCardMounted()) {
                    path = getExtSDCardPath();
                }
                break;
            case iStorageType.EXTERNAL_PRIVATE_MODE:
                File privateModeRoot = SemFwWrapper.file(PrivateModeMgr.getInstance(context).getRootDir());
                if (privateModeRoot.exists()) {
                    path = PrivateModeMgr.getInstance(context).getRootDir();
                }
                break;
            case iStorageType.EXTERNAL_USB_DRIVE_A:
            case iStorageType.EXTERNAL_USB_DRIVE_B:
            case iStorageType.EXTERNAL_USB_DRIVE_C:
            case iStorageType.EXTERNAL_USB_DRIVE_D:
            case iStorageType.EXTERNAL_USB_DRIVE_E:
            case iStorageType.EXTERNAL_USB_DRIVE_F:
                if (isUsbStorageMounted()) {
                    path = getUsbStoragePath(storageType);
                }
                break;
            default:
                break;
        }
        return path;
    }

    public static int getMatchedStorageType(String path) {
        if (path != null) {
            if (sUsbStoragePathArray != null) {
                for (int i = 0; i <= sUsbStoragePathArray.size(); i++) {
                    String usbPath = sUsbStoragePathArray.get(i + iStorageType.EXTERNAL_USB_DRIVE_A);
                    if (usbPath != null && path.startsWith(usbPath)) {
                        return (i + iStorageType.EXTERNAL_USB_DRIVE_A);
                    }
                }
            }
            if (isInternalPath(path)) {
                return iStorageType.INTERNAL;
            } else if (isSdCardPath(path)) {
                return iStorageType.EXTERNAL_SD;
            } else if (FileUtils.isPrivateFolder(path)) {
                return iStorageType.EXTERNAL_PRIVATE_MODE;
            }
        }
        return iStorageType.UNKNOWN;
    }

    public static String getBucketDisplayName(Context context, String path) {
        String displayedPath = path.substring(path.lastIndexOf('/') + 1);

        if (!TextUtils.isEmpty(path)) {
            if (sUsbStoragePathArray != null) {
                for (int i = 0; i < sUsbStoragePathArray.size(); i++) {
                    String usbPath = sUsbStoragePathArray.get(i + iStorageType.EXTERNAL_USB_DRIVE_A);
                    if (usbPath != null && path.equals(usbPath)) {
                        return getUsbStorageName(context, i + iStorageType.EXTERNAL_USB_DRIVE_A);
                    }
                }
            }
            if (path.equals(StoragePath.INTERNAL_ROOT)) {
                displayedPath = context.getString(UiUtils.getInternalStorageRootResId());
            } else if (path.equals(getExtSDCardPath())) {
                displayedPath = context.getString(R.string.sd_card);
            }
        }
        return displayedPath;
    }

    public static String getDisplayName(Context context, String path) {
        String displayedPath = null;

        if (!TextUtils.isEmpty(path)) {
            if (sUsbStoragePathArray != null) {
                for (int i = 0; i < sUsbStoragePathArray.size(); i++) {
                    String usbPath = sUsbStoragePathArray.get(i + iStorageType.EXTERNAL_USB_DRIVE_A);
                    if (usbPath != null && path.startsWith(usbPath)) {
                        return getUsbStorageName(context, i + iStorageType.EXTERNAL_USB_DRIVE_A);
                    }
                }
            }
            if (isInternalPath(path)) {
                displayedPath = context.getString(UiUtils.getInternalStorageRootResId());
            } else if (FileUtils.isUnderPath(getExtSDCardPath(), path)) {
                // It can be changed.
                displayedPath = context.getString(R.string.sd_card);
            } else if (FileUtils.isPrivateFolder(path)) {
                // It can be changed.
                displayedPath = context.getString(R.string.private_storage);
            } else if (path.startsWith(StoragePath.ROOT)) {
                displayedPath = context.getString(R.string.my_device);
            } else if (path.startsWith(StoragePath.RECENT_FILES)) {
                displayedPath = context.getString(R.string.subtitle_recent);
            } else if (path.startsWith(StoragePath.CATEGORY_IMAGE)) {
                displayedPath = context.getString(R.string.images);
            } else if (path.startsWith(StoragePath.CATEGORY_VIDEO)) {
                displayedPath = context.getString(R.string.videos);
            } else if (path.startsWith(StoragePath.CATEGORY_AUDIO)) {
                displayedPath = context.getString(R.string.audio);
            } else if (path.startsWith(StoragePath.CATEGORY_DOCUMENT)) {
                displayedPath = context.getString(R.string.documents);
            } else if (path.startsWith(StoragePath.CATEGORY_APK)) {
                displayedPath = context.getString(UiUtils.getInstallationFileString());
            } else if (path.startsWith(StoragePath.DOWNLOADS)) {
                displayedPath = context.getString(R.string.downloads);
            } else if (path.startsWith(StoragePath.GOOGLE_DRIVE_FOLDER)) {
                displayedPath = context.getString(R.string.google_drive);
            } else if (path.startsWith(StoragePath.SAMSUNG_DRIVE_FOLDER)) {
                displayedPath = context.getString(UiUtils.getSamsungDriveString());
            } else if (path.startsWith(StoragePath.DOWNLOAD_HISTORY)) {
                displayedPath = context.getString(R.string.downloads);
            } else if (path.startsWith(StoragePath.SHORTCUT_FILES)) {
                displayedPath = context.getString(R.string.sheader_shortcuts);
            }
        }
        return displayedPath;
    }

    private static StatFs getStatFS(String path) {
        StatFs stat = null;
        if (path != null)
            try {
                stat = new StatFs(path);
            } catch (Exception e) {
                Log.d(TAG, "path " + path);
            }
        return stat;
    }

    public static long getStorageFreeSpace(Context context, int storageType) {
        long capacity = 0;
        if (storageType == iStorageType.INTERNAL) {
            capacity = getDeviceStorageFreeSpace();
        } else {
            String path = getStoragePath(context, storageType);
            StatFs stat = getStatFS(path);
            if (stat != null) {
                capacity = stat.getAvailableBytes();
            } else {
                capacity = -1L;
            }
        }
        return capacity;
    }

    public static long getDeviceStorageFreeSpace() {
        long capacity = 0;
        // "/data" path is real partition of "/storage/emulated/0".
        // "/data" is larger than "/storage/emulated/0" about 20MB because
        // memory full issue.
        String path = "/data";
        StatFs stat = getStatFS(path);
        if (stat != null) {
            capacity = stat.getAvailableBytes();
        } else {
            capacity = -1L;
        }
        return capacity;
    }

    private static long correctionStorageSize(long totalSize) {
        int power = 2; // The power for the minimum total space. This value can be different as per the system.
        long tempTotalSize = 0;
        //long mRealTotalSize = Settings.System.getLong(mContext.getContentResolver(), "storage_mmc_size", SYSTEM_MEMORY_REAL_SIZE);
        StatFs statFs = new StatFs(Environment.getRootDirectory().getPath());
        long realTotalSize = totalSize + statFs.getTotalBytes();

        while (true) {
            tempTotalSize = SYSTEM_MEMORY_UNIT_GB * (long) Math.pow(2, power);
            if (realTotalSize <= tempTotalSize) {
                break;
            } else {
                power++;
            }
        }
        return tempTotalSize;
    }

    public static long getStorageSize(Context context, int storageType) {
        long capacity = 0;
        String path = getStoragePath(context, storageType);
        if (path != null) {
            File file = SemFwWrapper.file(path);
            capacity = file.getTotalSpace();
        }

        if (storageType == iStorageType.INTERNAL) {
            return correctionStorageSize(capacity);
        }
        return capacity;
    }

    public static String convertPathToDisplayedPath(Context context, String path) {
        String result = path;
        String storageName = getDisplayName(context, path);
        String storagePath = getStoragePath(context, getMatchedStorageType(path));
        if (!TextUtils.isEmpty(storagePath)) {
            if (!UiUtils.isInRTLMode(context)) {
                result = '/' + storageName + path.replaceFirst(storagePath, "");
            } else {
                result = '/' + storageName + getRtlPathString(path.replaceFirst(storagePath, ""));
            }
        }
        return result;
    }

    private static String getRtlPathString(String pathString) {
        String rtlPathString = "";
        StringTokenizer st = new StringTokenizer(pathString, "/");
        while (st.hasMoreTokens()) {
            String token = st.nextToken();
            rtlPathString = "/" + rtlPathString;
            if (UiUtils.isArabic(token)) {
                rtlPathString = "\u202a" + token + rtlPathString;
            } else {
                rtlPathString = token + rtlPathString;
            }
        }
        rtlPathString = "\u202a" + rtlPathString;
        return rtlPathString;
    }


    public static String getStorageDisplayName(Context context, String path) {
        if (StoragePath.INTERNAL_ROOT.equals(path)) {
            return context.getString(UiUtils.getInternalStorageRootResId());
        }

        if (StoragePath.PRIVATE_ROOT.equals(path)) {
            return context.getString(R.string.private_storage);
        }

        if (getExtSDCardPath().equals(path)) {
            return context.getString(R.string.sd_card);
        }

        if (StorageMonitor.isUsbRoot(path)) {
            return context.getString(R.string.usb_storage) + " " + path.substring(path.lastIndexOf('/') + 1);
        }

        return FileRecord.getName(path);
    }

    public static boolean isPrivateModeDirMounted(Context context) {
        return PrivateModeMgr.getInstance(context).isPrivateDirMounted();
    }

    /**
     * It return sd card is encrypted or not.
     *
     * @return true : encrypted
     * @seealso getSdCardEncrypted
     */
    public static boolean isSdCardEncrypted() {
        return sIsSdCardEncrypted;
    }


    /**
     * It check that SD Card is Encrypted or not.
     * it called by updateSubSystemStorageState and user should get value by isSdCardEncrypted.
     *
     * @param context
     * @return
     * @seealso isSdCardEncrypted
     */
    private static boolean getSdCardEncrypted(Context context) {
        boolean ret = false;
        DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
        try {
            Class<?> m = Class.forName("android.app.admin.DevicePolicyManager");
            Method method = m.getMethod("getSamsungSDcardEncryptionStatus", ComponentName.class);
            Object obj = method.invoke(dpm, (ComponentName) null);
            ret = (boolean) obj;
        } catch (ClassNotFoundException |
                NoSuchMethodException |
                InvocationTargetException |
                IllegalAccessException e) {
            Log.e(TAG, e.toString());
        }
        return ret;
    }

    public static boolean isSdCardWriteRestricted(Context context) {
        boolean ret = false;
        //TODO HYL Storage
        return ret;
    }

    public static boolean isSSDPowerPacked(Context context) {
        boolean isSSD = false;
        UsbManager manager = (UsbManager) context.getSystemService(Context.USB_SERVICE);

        if (manager != null) {
            HashMap<String, UsbDevice> deviceList = manager.getDeviceList();

            for (UsbDevice device : deviceList.values()) {
                if (device != null) {
                    if ((device.getVendorId() == 0x04E8) && (device.getProductId() == 0xA021) && ("SSD Power Pack".equals(device.getProductName()))) {
                        isSSD = true;
                        break;
                    }
                }
            }
        }
        return isSSD;
    }

    public static void setInternalStorageSize(Context context) {
        sInternalTotalSize = StorageMonitor.getStorageSize(context, StorageMonitor.iStorageType.INTERNAL);
    }

    public static long getInternalTotalSize(Context context) {
        if (sInternalTotalSize == 0) {
            setInternalStorageSize(context);
        }
        return sInternalTotalSize;
    }

}

